using System;
using System.Collections.Generic;
using System.Text;
using NBusiness.CodeDom;
using System.CodeDom;
using Microsoft.CSharp;
using NBusiness.Data;
using System.IO;
using System.CodeDom.Compiler;
using NBusiness.Templates.Contracts;
using NBusiness.CodeDom.Compiler;
using NBusiness.ESharp.Compiler;
using Microsoft.VisualBasic;
using NBusiness.Properties;
using NBusiness.Utilities;

namespace NBusiness.Templates
{
    [ResourceDescription("EntityInfoDescription")]
    public class EntityInfoTemplate : ITemplate, IInfoObject
    {
        #region Generate
        public EntityResourceFile[] Generate(PartialLanguage partialLanguage, params Entity[] entities)
        {
            List<EntityResourceFile> files = new List<EntityResourceFile>();
            foreach (Entity entity in entities)
            {
                CodeCompileUnit unit = new CodeCompileUnit();
                CodeNamespace n = new CodeNamespace((entity.Parent as IFamilyUnit).Fullname);
                string fileName = "EntityInfo\\" + entity.Fullname + "Info." + partialLanguage.ToString().ToLower();
               
                n.Comments.Add(new CodeCommentStatement(fileName + " - '" + this.GetType().FullName + "'"));
                n.Comments.Add(new CodeCommentStatement("This file was automatically generated by NBusiness"));
                n.Comments.Add(new CodeCommentStatement("on " + DateTime.Now.ToLongTimeString()));
                
                n.Imports.Add(new CodeNamespaceImport("System"));
                n.Imports.Add(new CodeNamespaceImport("System.ComponentModel"));
                unit.Namespaces.Add(n);

                CodeTypeDeclaration d = CreateMainClass(entity);
                n.Types.Add(d);

                CreateConstructors(entity, d);
                CreateFieldsAndProperties(entity, d);

                StringWriter sw = new StringWriter();
                CodeGeneratorOptions op = new CodeGeneratorOptions();
                CodeDomProvider provider = null;
                switch (partialLanguage)
                {
                    case PartialLanguage.CS:
                        provider = new CSharpCodeProvider();
                        op.BracingStyle = "C";
                        op.ElseOnClosing = false;
                        break;
                    case PartialLanguage.VB:
                        provider = new VBCodeProvider();
                        break;
                }
                op.BlankLinesBetweenMembers = false;
                op.IndentString = "\t";
                op.VerbatimOrder = true;
                provider.GenerateCodeFromCompileUnit(unit, sw, op);

                string code = sw.GetStringBuilder().ToString();
                byte[] data = Encoding.UTF8.GetBytes(code);

                EntityResourceFile file = new EntityResourceFile(
                    fileName,
                    EntityBuildAction.Compile);
                file.Data = data;

                files.Add(file);
            }
            return files.ToArray();
        } 
        #endregion

        #region Create Main Class
        protected virtual CodeTypeDeclaration CreateMainClass(Entity entity)
        {
            CodeCommentStatement createdBy = new CodeCommentStatement("This file was automatically generated by NBusiness");
            CodeTypeDeclaration d = new CodeTypeDeclaration(entity.Name + "Info");
            d.IsClass = true;
            d.IsPartial = true;
            d.Attributes = MemberAttributes.Public;
            d.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(SerializableAttribute))));

            return d;
        }
        #endregion

        #region Create Constructors
        protected virtual void CreateConstructors(Entity entity, CodeTypeDeclaration declaration)
        {
            CreateParameterlessConstructor(entity, declaration);
            CreateFullParametersConstructor(entity, declaration);
        }
        #region Create Parameterless Constructor
        protected virtual void CreateParameterlessConstructor(Entity entity, CodeTypeDeclaration declaration)
        {
            CodeConstructor c = new CodeConstructor();
            c.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, "Constructors"));
            c.Name = entity.Name + "Info";
            c.Attributes = MemberAttributes.Public;

            declaration.Members.Add(c);
        }
        #endregion
        #region Create Primary Fetch Constructor
        protected virtual void CreateFullParametersConstructor(Entity entity, CodeTypeDeclaration declaration)
        {
            CodeConstructor c1 = new CodeConstructor();
            c1.Name = entity.Name + "Info";
            c1.Attributes = MemberAttributes.Public;
            c1.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));

            foreach (EntityField f in entity.Fields)
            {
                CodeParameterDeclarationExpression p = new CodeParameterDeclarationExpression(
                    f.Type,
                    f.Name);
                c1.Parameters.Add(p);

                CodeAssignStatement assign = new CodeAssignStatement(
                    new CodeFieldReferenceExpression(
                        null,
                        "_" + f.Name),
                    new CodeVariableReferenceExpression(f.Name));

                c1.Statements.Add(assign);
            }

            declaration.Members.Add(c1);
        }
        #endregion
        #endregion

        #region Create Fields and Properties
        protected virtual void CreateFieldsAndProperties(Entity entity, CodeTypeDeclaration declaration)
        {
            int index = declaration.Members.Count;
            foreach (EntityField field in entity.Fields)
            {
                //create the field
                CodeMemberField f = new CodeMemberField(field.Type, "_" + field.Name);
                f.Attributes = MemberAttributes.Private;

                //create the property
                CodeMemberProperty p = new CodeMemberProperty();
                p.Name = field.Name;
                p.Attributes = MemberAttributes.Public | MemberAttributes.Final;
                p.Type = new CodeTypeReference(field.Type);
                CodeAttributeDeclaration att = new CodeAttributeDeclaration("DataObjectFieldAttribute");
                p.CustomAttributes.Add(att);

                if (field.IsId)
                {
                    att.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(true)));
                }
                else att.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(false)));

                if (field.IsAuto)
                {
                    att.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(true)));
                }
                else att.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(false)));

                if (field.IsNullable)
                {
                    att.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(true)));
                }
                else att.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(false)));

                CodeAttributeDeclaration bindable = new CodeAttributeDeclaration("BindableAttribute");
                bindable.Arguments.Add(new CodeAttributeArgument(new CodePrimitiveExpression(true)));
                p.CustomAttributes.Add(bindable);

                p.GetStatements.Add(
                    new CodeMethodReturnStatement(
                        new CodeFieldReferenceExpression(null, "_" + field.Name)));

                p.SetStatements.Add(
                    new CodeAssignStatement(
                        new CodeFieldReferenceExpression(null, "_" + field.Name),
                        new CodePropertySetValueReferenceExpression()));

                declaration.Members.Add(f);
                declaration.Members.Add(p);
            }
            declaration.Members[index].StartDirectives.Add(
                new CodeRegionDirective(CodeRegionMode.Start, "Member Fields and Properties"));

            index += (entity.Fields.Count * 2);
            declaration.Members[index - 1].EndDirectives.Add(
                new CodeRegionDirective(CodeRegionMode.End, ""));
        }
        #endregion

        #region Warning
        public event EventHandler<EntityBuildErrorEventArgs> Warning;
        protected virtual void OnWarning(ESharpBuildWarning warning)
        {
            if (Warning != null) Warning(this, new EntityBuildErrorEventArgs(warning));
        } 
        #endregion

        #region IInfoObjectMembers

        public CodeMemberMethod GetInfoObjectMethod(Entity entity)
        {
            CodeMemberMethod m = new CodeMemberMethod();
            m.Name = "GetInfoObject";
            m.Attributes = MemberAttributes.Public;
            m.ReturnType = new CodeTypeReference(entity.Name + "Info");
            m.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, "GetInfoObject"));
            m.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));

            CodeObjectCreateExpression create = new CodeObjectCreateExpression(entity.Name + "Info");
            CodeVariableDeclarationStatement info = new CodeVariableDeclarationStatement(
                entity.Name + "Info",
                "info",
                create);

            foreach (EntityField field in entity.Fields)
            {
                create.Parameters.Add(new CodeFieldReferenceExpression(
                    null,
                    field.Name));
            }

            m.Statements.Add(info);
            m.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression("info")));

            return m;
        }

        public CodeMemberMethod ToInfoArrayMethod(Entity entity)
        {
            CodeMemberMethod m = new CodeMemberMethod();
            m.Name = "ToInfoArray";
            m.Attributes = MemberAttributes.Public;
            m.ReturnType = new CodeTypeReference(entity.Name + "Info[]");
            m.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, "ToInfoArray"));
            m.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));

            CodeObjectCreateExpression create = new CodeObjectCreateExpression(
                new CodeTypeReference("List", new CodeTypeReference(entity.Name + "Info")));

            CodeVariableDeclarationStatement list = new CodeVariableDeclarationStatement(
                new CodeTypeReference("List", new CodeTypeReference(entity.Name + "Info")),
                "list",
                create);

            CodeVariableReferenceExpression x = new CodeVariableReferenceExpression("x");
            CodeIterationStatement itemIterate = new CodeIterationStatement(
                new CodeVariableDeclarationStatement(typeof(int), "x", new CodePrimitiveExpression(0)),
                new CodeBinaryOperatorExpression(
                    x,
                    CodeBinaryOperatorType.LessThan,
                    new CodePropertyReferenceExpression(null, "Count")),
                new CodeAssignStatement(
                    x,
                    new CodeBinaryOperatorExpression(
                        x,
                        CodeBinaryOperatorType.Add,
                        new CodePrimitiveExpression(1))));

            itemIterate.Statements.Add(
                new CodeMethodInvokeExpression(
                    new CodeVariableReferenceExpression("list"),
                    "Add",
                    new CodeMethodInvokeExpression(
                    new CodeArrayIndexerExpression(
                        new CodeThisReferenceExpression(),
                        x),
                    "GetInfoObject")));

            m.Statements.Add(list);
            m.Statements.Add(itemIterate);
            m.Statements.Add(new CodeMethodReturnStatement(
                new CodeMethodInvokeExpression(
                    new CodeVariableReferenceExpression("list"),
                    "ToArray")));

            return m;
        }

        public CodeMemberMethod LoadByInfoObjectMethod(Entity entity)
        {
            CodeMemberMethod m = new CodeMemberMethod();
            m.Name = "Load";
            m.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            m.ReturnType = new CodeTypeReference(entity.Name);
            m.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, "Load by Info Object"));
            m.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            m.Parameters.Add(new CodeParameterDeclarationExpression(entity.Name + "Info", "info"));

            CodeVariableReferenceExpression info = new CodeVariableReferenceExpression("info");

            CodeVariableDeclarationStatement objectReaderCreate = new CodeVariableDeclarationStatement(
                new CodeTypeReference("ObjectDataReader", new CodeTypeReference(entity.Name + "Info")),
                "objectReader",
                new CodeObjectCreateExpression(
                        new CodeTypeReference("ObjectDataReader", new CodeTypeReference(entity.Name + "Info")),
                        info));
            CodeVariableReferenceExpression readerRef = new CodeVariableReferenceExpression("objectReader");

            CodeMethodInvokeExpression readInvoke = new CodeMethodInvokeExpression(
                readerRef,
                "Read");

            CodeObjectCreateExpression readerCreate = new CodeObjectCreateExpression(entity.Name, 
                new CodeObjectCreateExpression(
                    new CodeTypeReference(typeof(DataReader)),
                    readerRef),
                new CodeObjectCreateExpression(entity.Name + "InfoCriteria"));

            m.Statements.Add(objectReaderCreate);
            m.Statements.Add(readInvoke);
            m.Statements.Add(new CodeMethodReturnStatement(readerCreate));

            return m;
        }

        public CodeTypeMember GetLoadByInfoObjectCriteriaDeclaration(Entity entity)
        {
            CodeTypeDeclaration criteria = new CodeTypeDeclaration(entity.Name + "InfoCriteria");
            criteria.Attributes = MemberAttributes.Private;
            criteria.BaseTypes.Add(new CodeTypeReference(typeof(CriteriaBase)));
            criteria.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, entity.Name + "InfoCriteria"));
            criteria.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));

            return criteria;
        }

        #endregion
    }
}
